﻿// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Quantum.QsCompiler

open System
open System.Collections.Immutable
open System.Text.RegularExpressions
open Microsoft.Quantum.QsCompiler.DataTypes
open Microsoft.Quantum.QsCompiler.ReservedKeywords
open Microsoft.Quantum.QsCompiler.SyntaxExtensions
open Microsoft.Quantum.QsCompiler.SyntaxTokens
open Microsoft.Quantum.QsCompiler.SyntaxTree
open Microsoft.Quantum.QsCompiler.Transformations.Core


// transformations used to strip range information for auto-generated syntax

type private StripPositionInfoFromType(parent: StripPositionInfo) =
    inherit TypeTransformation(parent)

    override this.OnTypeRange _ = TypeRange.Generated

    override this.OnRangeInformation _ = Null

and private StripPositionInfoFromExpression(parent: StripPositionInfo) =
    inherit ExpressionTransformation(parent)
    override this.OnRangeInformation _ = Null

and private StripPositionInfoFromStatement(parent: StripPositionInfo) =
    inherit StatementTransformation(parent)
    override this.OnLocation _ = Null

and private StripPositionInfoFromNamespace(parent: StripPositionInfo) =
    inherit NamespaceTransformation(parent)
    override this.OnLocation _ = Null

and public StripPositionInfo private (_internal_) =
    inherit SyntaxTreeTransformation()
    static let defaultInstance = new StripPositionInfo()

    new() as this =
        StripPositionInfo("_internal_")
        then
            this.Types <- new StripPositionInfoFromType(this)
            this.Expressions <- new StripPositionInfoFromExpression(this)
            this.Statements <- new StripPositionInfoFromStatement(this)
            this.Namespaces <- new StripPositionInfoFromNamespace(this)

    static member public Default = defaultInstance
    static member public Apply t = defaultInstance.Types.OnType t

    static member public Apply e =
        defaultInstance.Expressions.OnTypedExpression e

    static member public Apply s = defaultInstance.Statements.OnScope s

    static member public Apply a =
        defaultInstance.Namespaces.OnNamespace a


module SyntaxGenerator =

    /// Matches only if the string consists of a fully qualified name and nothing else.
    let internal FullyQualifiedName = new Regex(@"^[\p{L}_][\p{L}\p{Nd}_]*(\.[\p{L}_][\p{L}\p{Nd}_]*)+$")


    // literal expressions

    /// Builds an immutable typed expression of the given kind and type kind,
    /// setting the quantum dependency to the given value and assuming no type parameter resolutions.
    /// Sets the range information for the built expression to Null.
    let AutoGeneratedExpression kind exTypeKind qDep =
        let inferredInfo = InferredExpressionInformation.New(false, quantumDep = qDep)
        TypedExpression.New(kind, ImmutableDictionary.Empty, exTypeKind |> ResolvedType.New, inferredInfo, Null)

    /// Creates a typed expression that represents an invalid expression of invalid type.
    /// Sets the range information for the built expression to Null.
    let InvalidExpression = AutoGeneratedExpression InvalidExpr QsTypeKind.InvalidType false

    /// Creates a typed expression that corresponds to a Unit value.
    /// Sets the range information for the built expression to Null.
    let UnitValue = AutoGeneratedExpression UnitValue QsTypeKind.UnitType false

    /// Creates a typed expression that corresponds to a Bool literal with the given value.
    /// Sets the range information for the built expression to Null.
    let BoolLiteral value =
        AutoGeneratedExpression(BoolLiteral value) QsTypeKind.Bool false

    /// Creates a typed expression that corresponds to an Int literal with the given value.
    /// Sets the range information for the built expression to Null.
    let IntLiteral v =
        AutoGeneratedExpression(IntLiteral v) QsTypeKind.Int false

    /// Creates a typed expression that corresponds to a BigInt literal with the given value.
    /// Sets the range information for the built expression to Null.
    let BigIntLiteral (v: int) =
        AutoGeneratedExpression(BigIntLiteral(bigint v)) QsTypeKind.BigInt false

    /// Creates a typed expression that corresponds to a Double literal with the given value.
    /// Sets the range information for the built expression to Null.
    let DoubleLiteral v =
        AutoGeneratedExpression(DoubleLiteral v) QsTypeKind.Double false

    /// Creates a typed expression that corresponds to a String literal with the given value and interpolation arguments.
    /// Sets the range information for the built expression to Null.
    let StringLiteral (s, interpolArgs) =
        AutoGeneratedExpression(StringLiteral(s, interpolArgs)) QsTypeKind.String false

    /// Creates a typed expression that corresponds to a Range literal with the given left hand side and right hand side.
    /// Sets the range information for the built expression to Null.
    /// Does *not* verify the given left and right hand side.
    let RangeLiteral (lhs, rhs) =
        AutoGeneratedExpression(RangeLiteral(lhs, rhs)) QsTypeKind.Range false

    /// Creates a typed expression that corresponds to a value tuple with the given items.
    /// Sets the range information for the built expression to Null.
    /// Does *not* strip positional information from the given items; the responsibility to do so is with the caller.
    let TupleLiteral (items: TypedExpression seq) =
        let qdep = items |> Seq.exists (fun item -> item.InferredInformation.HasLocalQuantumDependency)

        let tupleType =
            items |> Seq.map (fun item -> item.ResolvedType) |> ImmutableArray.CreateRange |> TupleType

        AutoGeneratedExpression(ValueTuple(items.ToImmutableArray())) tupleType qdep

    // utils for assignments

    /// Given a typed expression that consists only of local variables, missing or invalid expressions, and tuples thereof,
    /// returns the corresponding symbol tuple. Throws an ArgumentException if the expression contains anything else.
    let rec ExpressionAsSymbolTuple (ex: TypedExpression) : SymbolTuple =
        match ex.Expression with
        | ValueTuple items ->
            items |> Seq.map ExpressionAsSymbolTuple |> ImmutableArray.CreateRange |> VariableNameTuple
        | Identifier (LocalVariable varName, tArgs) when tArgs = Null -> VariableName varName
        | Identifier (InvalidIdentifier, tArgs) when tArgs = Null -> InvalidItem
        | InvalidExpr -> InvalidItem
        | MissingExpr -> DiscardedItem
        | _ ->
            ArgumentException "the given expression contains items that do not represent a valid symbol name"
            |> raise

    /// Given the argument tuple of a callable or a provided specialization declaration, returns the corresponding symbol tuple.
    /// Throws an ArgumentExecption if the outermost tuple is empty or if it is not a QsTuple, or if an inner tuple is empty.
    let ArgumentTupleAsSymbolTuple (argTuple: QsTuple<LocalVariableDeclaration<QsLocalSymbol>>) : SymbolTuple =
        let rec resolveArgTupleItem =
            function
            | QsTupleItem (decl: LocalVariableDeclaration<QsLocalSymbol>) ->
                decl.VariableName
                |> function
                    | ValidName varName -> varName |> VariableName
                    | InvalidName -> DiscardedItem
            | QsTuple elements when elements.Length = 0 ->
                ArgumentException "argument tuple items cannot be empty tuples" |> raise
            | QsTuple elements when elements.Length = 1 -> resolveArgTupleItem elements.[0]
            | QsTuple elements -> buildTuple elements

        and buildTuple elements =
            let items = elements |> Seq.map resolveArgTupleItem |> ImmutableArray.CreateRange
            items |> VariableNameTuple

        match argTuple with
        | QsTuple elements when elements.Length = 0 ->
            ArgumentException "cannot construct symbol tuple for empty argument tuple" |> raise
        | QsTuple elements when elements.Length = 1 -> resolveArgTupleItem elements.[0]
        | QsTuple elements -> buildTuple elements
        | _ -> ArgumentException "the argument tuple needs to be a QsTuple" |> raise

    /// Given the argument tuple of a callable or a provided specialization declaration,
    /// returns the corresponding typed expression.
    /// Throws an ArgumentExecption if the outermost tuple is not a QsTuple, or if an inner tuple is empty.
    let ArgumentTupleAsExpression (argTuple: QsTuple<LocalVariableDeclaration<QsLocalSymbol>>) =
        let rec resolveArgTupleItem =
            function
            | QsTupleItem (decl: LocalVariableDeclaration<QsLocalSymbol>) ->
                decl.VariableName
                |> function
                    | ValidName varName ->
                        let exKind = (LocalVariable varName, Null) |> Identifier
                        AutoGeneratedExpression exKind decl.Type.Resolution false
                    | InvalidName -> InvalidExpression
            | QsTuple elements when elements.Length = 0 ->
                ArgumentException "argument tuple items cannot be empty tuples" |> raise
            | QsTuple elements when elements.Length = 1 -> resolveArgTupleItem elements.[0]
            | QsTuple elements -> buildTuple elements

        and buildTuple elements =
            let items = elements |> Seq.map resolveArgTupleItem |> ImmutableArray.CreateRange
            let exType = items |> Seq.map (fun ex -> ex.ResolvedType) |> ImmutableArray.CreateRange |> TupleType
            AutoGeneratedExpression(ValueTuple items) exType false

        match argTuple with
        | QsTuple elements when elements.Length = 0 -> UnitValue
        | QsTuple elements when elements.Length = 1 -> resolveArgTupleItem elements.[0]
        | QsTuple elements -> buildTuple elements
        | _ -> ArgumentException "the argument tuple needs to be a QsTuple" |> raise

    // utils to for building typed expressions and iterable inversions

    /// Creates a typed expression corresponding to a call to a non-type-parametrized callable.
    /// Only verifies the type of <paramref name="lhs"/>.
    /// </summary>
    /// <exception cref="ArgumentException">The type of <paramref name="lhs"/> is valid but not a <see cref="QsTypeKind.Function"/> or <see cref="QsTypeKind.Operation"/> type.</exception>.
    let CallNonGeneric (lhs: TypedExpression, rhs: TypedExpression) =
        let kind = CallLikeExpression(lhs, rhs)

        let quantumDep =
            lhs.InferredInformation.HasLocalQuantumDependency
            || rhs.InferredInformation.HasLocalQuantumDependency

        match lhs.ResolvedType.Resolution with
        | QsTypeKind.InvalidType -> AutoGeneratedExpression kind InvalidType quantumDep
        | QsTypeKind.Operation ((_, ot), _)
        | QsTypeKind.Function (_, ot) -> AutoGeneratedExpression kind ot.Resolution quantumDep
        | _ -> ArgumentException "given lhs is not callable" |> raise

    /// <summary>
    /// Given a typed expression of type range,
    /// creates a typed expression that when executed will generate the reverse sequence for the given range.
    /// Assumes that the RangeReverse function is part of the standard library.
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="ex"/> is of a valid type but not of type <see cref="QsTypeKind.Range"/>.</exception>
    let private ReverseRange (ex: TypedExpression) =
        let buildCallToReverse ex =
            let kind = Identifier.GlobalCallable BuiltIn.RangeReverse.FullName

            let exTypeKind =
                QsTypeKind.Function(QsTypeKind.Range |> ResolvedType.New, QsTypeKind.Range |> ResolvedType.New)

            let reverse = AutoGeneratedExpression(QsExpressionKind.Identifier(kind, Null)) exTypeKind false
            CallNonGeneric(reverse, ex)

        match ex.ResolvedType.Resolution with
        | QsTypeKind.InvalidType
        | QsTypeKind.Range _ -> buildCallToReverse ex
        | _ -> ArgumentException "given expression is not a range" |> raise

    /// Builds a range expression for the given lhs and rhs of the range operator.
    /// Does *not* verify the type of the given lhs or rhs.
    let private RangeExpression (lhs: TypedExpression, rhs: TypedExpression) =
        let kind = QsExpressionKind.RangeLiteral(lhs, rhs)

        let quantumDep =
            lhs.InferredInformation.HasLocalQuantumDependency
            || rhs.InferredInformation.HasLocalQuantumDependency

        AutoGeneratedExpression kind QsTypeKind.Range quantumDep

    /// <summary>
    /// Creates a typed expression that corresponds to a call to the Length function.
    /// The Length function needs to be part of the QsCore library, and its type parameter name needs to match the one here.
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="ex"/> is not of type array or is of an invalid type.</exception>
    let Length (ex: TypedExpression) =
        let callableName = BuiltIn.Length.FullName
        let kind = Identifier.GlobalCallable callableName

        let typeParameterName =
            match BuiltIn.Length.Kind with
            | BuiltInKind.Function typeParams -> typeParams.[0]
            | _ -> ArgumentException "Length is expected to be a function" |> raise

        let typeParameter = QsTypeParameter.New(callableName, typeParameterName)

        let genArrayType =
            QsTypeKind.ArrayType(QsTypeKind.TypeParameter typeParameter |> ResolvedType.New) |> ResolvedType.New

        let exTypeKind = QsTypeKind.Function(genArrayType, QsTypeKind.Int |> ResolvedType.New)
        let length = AutoGeneratedExpression(QsExpressionKind.Identifier(kind, Null)) exTypeKind false

        let callToLength tpRes =
            let resolutions = ImmutableArray.Create((typeParameter.Origin, typeParameter.TypeName, tpRes))
            { CallNonGeneric(length, ex) with TypeArguments = resolutions }

        match ex.ResolvedType.Resolution with
        | ArrayType b -> callToLength b
        | InvalidType -> callToLength ex.ResolvedType
        | _ -> ArgumentException "the given expression is not of array type" |> raise

    /// <summary>
    /// Creates a typed expression that corresponds to subtracting one from the call to the Length function.
    /// Sets any range information in the built expression to Null, including inner expressions.
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="ex"/> is not of type array or is of an invalid type.</exception>
    let LengthMinusOne (ex: TypedExpression) =
        let callToLength = ex |> StripPositionInfo.Apply |> Length
        let kind = QsExpressionKind.SUB(callToLength, IntLiteral 1L)
        AutoGeneratedExpression kind QsTypeKind.Int callToLength.InferredInformation.HasLocalQuantumDependency

    /// <summary>
    /// Given a typed expression of array type,
    /// creates a typed expression that when evaluated returns a new array with the order of the elements reversed.
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="ex"/> is not of type array or is of an invalid type.</exception>
    let private ReverseArray (ex: TypedExpression) =
        let built =
            let reversingRange = RangeExpression(RangeExpression(LengthMinusOne ex, IntLiteral -1L), IntLiteral 0L)
            let kind = ArrayItem(ex, reversingRange)
            AutoGeneratedExpression kind ex.ResolvedType.Resolution ex.InferredInformation.HasLocalQuantumDependency

        match ex.ResolvedType.Resolution with
        | ArrayType _
        | InvalidType -> built
        | _ -> ArgumentException "the given expression is not of array type" |> raise

    /// <summary>
    /// Given a typed expression of a type that supports iteration,
    /// creates a typed expression that when evaluated returns the reversed sequence.
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="ex"/> is of a valid type but not either of type <see cref="QsTypeKind.Range"/> or of array type.</exception>
    let ReverseIterable (ex: TypedExpression) =
        let ex = StripPositionInfo.Apply ex

        match ex.ResolvedType.Resolution with
        | QsTypeKind.Range -> ReverseRange ex
        | QsTypeKind.ArrayType _ -> ReverseArray ex
        | QsTypeKind.InvalidType -> ex
        | _ -> ArgumentException "the given expression is not iterable" |> raise

    /// <summary>
    /// Returns a boolean expression that evaluates to true if the given expression is negative.
    /// Returns an invalid expression of type Bool if the given expression is invalid.
    /// </summary>
    /// <exception cref="ArgumentException">The type of <paramref name="ex"/> does not support arithmetic.</exception>
    let IsNegative (ex: TypedExpression) =
        let kind =
            match ex.ResolvedType.Resolution with
            | Int -> LT(ex, IntLiteral 0L)
            | BigInt -> LT(ex, BigIntLiteral 0)
            | Double -> LT(ex, DoubleLiteral 0.)
            | InvalidType -> InvalidExpr
            | _ -> ArgumentException "the type of the given expression does not support arithmetic operations" |> raise

        AutoGeneratedExpression kind Bool ex.InferredInformation.HasLocalQuantumDependency

    /// Returns an expression that adds the two expression.
    /// The type of the expression is set to the given type and the position information is set to null.
    /// Does not validate type compatibility.
    let AddExpressions exType (lhs: TypedExpression, rhs: TypedExpression) =
        let kind = QsExpressionKind.ADD(lhs, rhs)

        let quantumDep =
            lhs.InferredInformation.HasLocalQuantumDependency
            || rhs.InferredInformation.HasLocalQuantumDependency

        AutoGeneratedExpression kind exType quantumDep

    // utils related to building and generating functor specializations

    let QubitArrayType = Qubit |> ResolvedType.New |> ArrayType |> ResolvedType.New

    /// Recursively extracts and returns all tuple items in the given tuple.
    let ExtractInnerItems (this: ITuple) =
        let rec extractAll =
            function
            | Tuple items -> items |> Seq.collect extractAll
            | Item item -> seq { yield item }
            | Missing -> ArgumentException "missing item in tuple" |> raise
            | _ -> ArgumentException "invalid item in tuple" |> raise

        this |> extractAll

    /// Given a QsTuple, recursively extracts and returns all of its items.
    let ExtractItems (this: QsTuple<_>) = this.Items.ToImmutableArray()

    /// Given an immutable array with local variable declarations returns an immutable array containing only the declarations with a valid name.
    let ValidDeclarations (this: ImmutableArray<LocalVariableDeclaration<QsLocalSymbol>>) =
        let withValidName (d: LocalVariableDeclaration<_>) =
            match d.VariableName with
            | ValidName name ->
                let mut, qDep = d.InferredInformation.IsMutable, d.InferredInformation.HasLocalQuantumDependency
                Some(LocalVariableDeclaration.New mut ((d.Position, d.Range), name, d.Type, qDep))
            | _ -> None

        (this |> Seq.choose withValidName).ToImmutableArray()

    /// Strips all range information from the given signature.
    let WithoutRangeInfo (signature: ResolvedSignature) =
        let argType = signature.ArgumentType |> StripPositionInfo.Apply
        let returnType = signature.ReturnType |> StripPositionInfo.Apply
        ResolvedSignature.New((argType, returnType), signature.Information, signature.TypeParameters)

    /// Given the resolved argument type of an operation, returns the argument type of its controlled version.
    let AddControlQubits (argT: ResolvedType) =
        [ QubitArrayType; argT ].ToImmutableArray() |> TupleType |> ResolvedType.New

    /// Given a resolved signature, returns the corresponding signature for the controlled version.
    let BuildControlled (this: ResolvedSignature) =
        { this with ArgumentType = this.ArgumentType |> AddControlQubits }

    /// <summary>
    /// Given an argument tuple of a callable, the name and the range of the control qubits symbol, as well as the position offset for that range,
    /// builds and returns the argument tuple for the controlled specialization.
    /// </summary>
    /// <exception cref="ArgumentException">The given argument tuple is not a <see cref="QsTuple"/>.</exception>
    let WithControlQubits arg offset (name, symRange: QsNullable<_>) =
        let range = symRange.ValueOr Range.Zero
        let ctlQs = LocalVariableDeclaration.New false ((offset, range), name, QubitArrayType, false)

        let unitArg =
            let argName = ValidName InternalUse.UnitArgument
            let unitT = UnitType |> ResolvedType.New
            LocalVariableDeclaration.New false ((offset, range), argName, unitT, false) |> QsTupleItem // the range is not accurate here, but also irrelevant

        match arg with
        | QsTuple ts when ts.Length = 0 -> [ ctlQs |> QsTupleItem; unitArg ].ToImmutableArray()
        | QsTuple ts when ts.Length = 1 -> [ ctlQs |> QsTupleItem; ts.[0] ].ToImmutableArray()
        | QsTuple _ -> [ ctlQs |> QsTupleItem; arg ].ToImmutableArray()
        | _ -> ArgumentException "expecting the given argument tuple to be a QsTuple" |> raise
        |> QsTuple

    /// <summary>
    /// Given a typed expression that is used as argument to an operation and a typed expression for the control qubits,
    /// combines them to a suitable argument for the controlled version of the originally called operation under the assumption that the argument was correct.
    /// The range information for the built expression is set to Null.
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="ctlQs"/> is a valid type, but not an <see cref="ArrayType"/> <see cref="Qubit"/>.</exception>
    let ArgumentWithControlQubits (arg: TypedExpression) (ctlQs: TypedExpression) =
        let isInvalid =
            function
            | Tuple _
            | Item _
            | Missing -> false
            | _ -> true

        if ctlQs.ResolvedType.Resolution <> QubitArrayType.Resolution && not (ctlQs.ResolvedType |> isInvalid) then
            new ArgumentException "expression for the control qubits is valid but not of type Qubit[]" |> raise

        TupleLiteral [ ctlQs; arg ]

    /// Returns the name of the control qubits
    /// if the given argument tuple is consistent with the argument tuple of a controlled specialization.
    /// Return null otherwise, or if the name of the control qubits is invalid.
    let ControlledFunctorArgument arg =
        let getItemName =
            function
            | QsTupleItem (item: LocalVariableDeclaration<QsLocalSymbol>) ->
                match item.VariableName with
                | ValidName name -> name
                | InvalidName -> null
            | _ -> null

        match arg with
        | QsTuple ts when ts.Length = 2 -> ts.[0] |> getItemName
        | _ -> null

    /// Creates a typed expression that corresponds to an immutable local variable with the given name
    /// that contains an expression of type Qubit[] and has no quantum dependencies.
    let ImmutableQubitArrayWithName name =
        let kind = (Identifier.LocalVariable name, Null) |> QsExpressionKind.Identifier
        let exTypeKind = QsTypeKind.ArrayType(QsTypeKind.Qubit |> ResolvedType.New)
        AutoGeneratedExpression kind exTypeKind false

    /// <summary>
    /// Given a typed expression of operation type, creates a typed expression corresponding to a Controlled application on that operation.
    /// Creates an expression of invalid type if the given expression is invalid.
    /// Blindly builds the controlled application if the characteristics of the given operation are invalid.
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="ex"/> is valid but not of type <see cref="QsTypeKind.Operation"/>, or does not support the <see cref="Controlled"/> functor.</exception>
    let ControlledOperation (ex: TypedExpression) =
        let ex = StripPositionInfo.Apply ex
        let kind = QsExpressionKind.ControlledApplication ex

        let built exTypeKind =
            AutoGeneratedExpression kind exTypeKind ex.InferredInformation.HasLocalQuantumDependency

        let ctlOpType ((it, ot), opInfo) =
            QsTypeKind.Operation((AddControlQubits it, ot), opInfo)

        match ex.ResolvedType.Resolution with
        | QsTypeKind.InvalidType -> built InvalidType
        | QsTypeKind.Operation ((it, ot), opInfo) when opInfo.Characteristics.AreInvalid ->
            ctlOpType ((it, ot), opInfo) |> built
        | QsTypeKind.Operation ((it, ot), opInfo) ->
            match opInfo.Characteristics.SupportedFunctors with
            | Value functors when functors.Contains Controlled -> ctlOpType ((it, ot), opInfo) |> built
            | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise
        | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise

    /// <summary>
    /// Given a typed expression of operation type, creates a typed expression corresponding to a Adjoint application on that operation.
    /// Creates an expression of invalid type if the given expression is invalid.
    /// Blindly builds the adjoint application if the characteristics of the given operation are invalid.
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="ex"/> is valid but not of type <see cref="QsTypeKind.Operation"/>, or does not support the <see cref="Adjoint"/> functor.</exception>
    let AdjointOperation (ex: TypedExpression) =
        let ex = StripPositionInfo.Apply ex
        let kind = QsExpressionKind.AdjointApplication(ex)

        let built =
            AutoGeneratedExpression kind ex.ResolvedType.Resolution ex.InferredInformation.HasLocalQuantumDependency

        match ex.ResolvedType.Resolution with
        | QsTypeKind.InvalidType -> built
        | QsTypeKind.Operation (_, opInfo) when opInfo.Characteristics.AreInvalid -> built
        | QsTypeKind.Operation (_, opInfo) ->
            match opInfo.Characteristics.SupportedFunctors with
            | Value functors when functors.Contains Adjoint -> built
            | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise
        | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise
